package com.neusoft.hifly.core.redis;

import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
import org.springframework.stereotype.Component;

@Component("redisCache")
public class RedisCache {

	@Autowired
	private RedisTemplate<String, String> redisTemplate;

	/**
	 * 强制添加过期时间（秒）：4小时左右
	 *
	 * @return 超时时间
	 */
	public static long timeout4() {
		return 4 * 60 * 60 + new Random().nextInt(10000);
	}

	/**
	 * 强制添加过期时间（秒）：24小时左右
	 *
	 * @return 超时时间
	 */
	public static long timeout24() {
		return 24 * 60 * 60 + new Random().nextInt(10000);
	}

	/**
	 * 强制添加过期时间（秒）：48小时左右
	 *
	 * @return 超时时间
	 */
	public static long timeout48() {
		return 48 * 60 * 60 + new Random().nextInt(10000);
	}

	//=============================common============================
	/**
	 * 指定缓存失效时间
	 *
	 * @param key
	 *            键
	 * @param time
	 *            时间(秒)
	 * @return 是否成功
	 */
	public boolean expire(final String key, final long time) {
		if (StringUtils.isEmpty(key)) {
			return false;
		}
		try {
			if (time > 0) {
				redisTemplate.expire(key, time, TimeUnit.SECONDS);
			}
			return true;
		} catch (final Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * 根据key 获取过期时间
	 *
	 * @param key
	 *            键 不能为null
	 * @return 时间(秒) 返回0代表为永久有效
	 */
	public long getExpire(final String key) {
		if (StringUtils.isEmpty(key)) {
			return 0;
		}
		return redisTemplate.getExpire(key, TimeUnit.SECONDS);
	}

	/**
	 * 判断key是否存在
	 *
	 * @param key
	 *            键
	 * @return true 存在 false不存在
	 */
	public boolean exists(final String key) {
		if (StringUtils.isEmpty(key)) {
			return false;
		}
		try {
			return redisTemplate.hasKey(key);
		} catch (final Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * 删除缓存
	 * <p>
	 * list可以用这个删除
	 *
	 * @param key
	 *            可以传一个值 或多个。
	 *            <p>
	 *            传入多个可变参数
	 *            <p>
	 *            1.调用可变参数方法时，可不传参数，也可以穿任意多个参数
	 *            <p>
	 *            2.但在可变参数方法内使用多个参数时，将array视为数组使用（此时必须有参数传入）
	 *            <p>
	 *            3.若该方法多个变量，可变参数必须放最后（可变参数只能一个）
	 */
	public void delete(final String... key) {
		if (key == null) {
			return;
		}
		if (key != null && key.length > 0) {
			for (final String k : key) {
				redisTemplate.delete(k);
			}
		}
	}

	//============================String=============================
	/**
	 * 缓存基本的对象，Integer、String、实体类等
	 *
	 * @param key
	 *            缓存的键值
	 * @param value
	 *            缓存的值
	 */
	public void set(final String key, final String value) {
		//		if (StringUtils.isEmpty(key)) {
		//			return;
		//		}
		//		redisTemplate.opsForValue().set(key, value);
		// 强制添加过期时间（秒）：4小时左右
		// TODO【技巧】解决redis缓存雪崩：在缓存的时候给过期时间加上一个随机值，这样就会大幅度的减少缓存在同一时间过期。
		this.set(key, value, timeout4());
	}

	/**
	 * 缓存基本的对象，Integer、String、实体类等
	 *
	 * @param key
	 *            缓存的键值
	 * @param value
	 *            缓存的值
	 * @param time
	 *            时间(秒) time要大于0 如果time小于等于0 将设置无限期
	 */
	public void set(final String key, final String value, final long time) {
		if (StringUtils.isEmpty(key)) {
			return;
		}
		redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
	}

	/**
	 * 获得缓存的基本对象。
	 *
	 * @param key
	 *            缓存键值
	 * @return 缓存键值对应的数据
	 */
	public String get(final String key) {
		if (StringUtils.isEmpty(key)) {
			return null;
		}
		return redisTemplate.opsForValue().get(key);
	}

	//============================Long=============================

	/**
	 * 递增
	 * <p>
	 * 此方法会先检查key是否存在，存在+1，不存在先初始化，再+1
	 *
	 * @param key
	 *            键
	 * @param by
	 *            要增加几(大于0)
	 * @return 数值
	 */
	public long incr(final String key, final long delta) {
		if (StringUtils.isEmpty(key)) {
			return 0;
		}
		if (delta < 0) {
			throw new RuntimeException("递增因子必须大于0");
		}
		return redisTemplate.opsForValue().increment(key, delta);
	}

	/**
	 * 递减
	 *
	 * @param key
	 *            键
	 * @param by
	 *            要减少几(小于0)
	 * @return 数值
	 */
	public long decr(final String key, final long delta) {
		if (StringUtils.isEmpty(key)) {
			return 0;
		}
		if (delta < 0) {
			throw new RuntimeException("递减因子必须大于0");
		}
		return redisTemplate.opsForValue().increment(key, -delta);
	}

	/**
	 * 获取数值
	 *
	 * @param key
	 *            键
	 * @param by
	 *            要减少几(小于0)
	 * @return 数值
	 */
	public long getLong(final String key) {
		if (StringUtils.isEmpty(key)) {
			return 0;
		}

		String str = redisTemplate.boundValueOps(key).get(0, -1);
		if (StringUtils.isEmpty(str)) {
			return 0;
		}
		return Long.valueOf(str);
	}

	//============================Bit=============================
	/**
	 * 缓存Bit
	 *
	 * @param key
	 *            缓存的键值
	 * @param offset
	 *            位置(从左向右数)
	 * @param value
	 *            对应的ascii码
	 */
	public <T> void setBit(final String key, final long offset, final boolean value) {
		if (StringUtils.isEmpty(key)) {
			return;
		}
		redisTemplate.opsForValue().setBit(key, offset, value);
	}

	/**
	 * 获得Bit
	 *
	 * @param key
	 *            缓存键值
	 * @param offset
	 *            位置(从左向右数)
	 * @return 缓存键值对应的数据
	 */
	public <T> Boolean getBit(final String key, final long offset) {
		if (StringUtils.isEmpty(key)) {
			return null;
		}
		return redisTemplate.opsForValue().getBit(key, offset);
	}

	//============================List=============================
	/**
	 * 缓存List数据
	 *
	 * @param key
	 *            缓存的键值
	 * @param obj
	 *            待缓存的数据
	 * @return 缓存的对象
	 */
	public void setList(final String key, final String obj) {
		//		if (StringUtils.isEmpty(key)) {
		//			return;
		//		}
		//		redisTemplate.opsForList().rightPushAll(key, obj);

		// TODO【技巧】解决redis缓存雪崩：在缓存的时候给过期时间加上一个随机值，这样就会大幅度的减少缓存在同一时间过期。
		this.setList(key, obj, timeout4());
	}

	/**
	 * 缓存List数据
	 *
	 * @param key
	 *            缓存的键值
	 * @param obj
	 *            待缓存的数据
	 * @param time
	 *            时间(秒)
	 * @return 缓存的对象
	 */
	public void setList(final String key, final String obj, final long time) {
		if (StringUtils.isEmpty(key)) {
			return;
		}
		redisTemplate.opsForList().rightPushAll(key, obj);
		if (time > 0) {
			expire(key, time);
		}
	}

	/**
	 * 缓存List数据
	 *
	 * @param key
	 *            缓存的键值
	 * @param dataList
	 *            待缓存的List数据
	 * @return 缓存的对象
	 */
	public void setList(final String key, final List<String> dataList) {
		if (StringUtils.isEmpty(key) || dataList == null || dataList.isEmpty()) {
			return;
		}
		redisTemplate.opsForList().rightPushAll(key, dataList);
	}

	/**
	 * 缓存List数据
	 *
	 * @param key
	 *            缓存的键值
	 * @param dataList
	 *            待缓存的List数据
	 * @param time
	 *            时间(秒)
	 * @return 缓存的对象
	 */
	public void setList(final String key, final List<String> dataList, final long time) {
		if (StringUtils.isEmpty(key)) {
			return;
		}
		redisTemplate.opsForList().rightPushAll(key, dataList);
		if (time > 0) {
			expire(key, time);
		}
	}

	/**
	 * 获得缓存的list对象
	 *
	 * @param key
	 *            缓存的键值
	 * @return 缓存键值对应的数据
	 */
	public List<String> getList(final String key) {
		if (StringUtils.isEmpty(key)) {
			return null;
		}
		final ListOperations<String, String> listOperation = redisTemplate.opsForList();
		final Long size = listOperation.size(key);
		return listOperation.range(key, 0, size);
	}

	/**
	 * 获得缓存的list对象
	 *
	 * @param key
	 *            缓存的键值
	 * @param start
	 *            开始
	 * @param end
	 *            结束 0 到 -1代表所有值
	 * @return 缓存键值对应的数据
	 */
	public List<String> getList(final String key, final long start, final long end) {
		if (StringUtils.isEmpty(key)) {
			return null;
		}
		return redisTemplate.opsForList().range(key, start, end);
	}

	/**
	 * 获取list缓存的长度
	 *
	 * @param key
	 *            键
	 * @return 长度
	 */
	public long getListSize(final String key) {
		if (StringUtils.isEmpty(key)) {
			return 0;
		}
		try {
			return redisTemplate.opsForList().size(key);
		} catch (final Exception e) {
			e.printStackTrace();
			return 0;
		}
	}

	/**
	 * 通过索引 获取list中的值
	 *
	 * @param key
	 *            键
	 * @param index
	 *            索引 index>=0时， 0 表头，1 第二个元素，依次类推；index<0时，-1，表尾，-2倒数第二个元素，依次类推
	 * @return
	 */
	public Object getListIndex(final String key, final long index) {
		if (StringUtils.isEmpty(key)) {
			return null;
		}
		try {
			return redisTemplate.opsForList().index(key, index);
		} catch (final Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * 从存储在键中的列表中删除等于值的元素的第一个计数事件
	 *
	 * @param key
	 *            键
	 * @param count
	 *            位置
	 *            <p>
	 *            count> 0：删除等于从头到尾移动的值的元素。
	 *            <p>
	 *            count <0：删除等于从尾到头移动的值的元素。
	 *            <p>
	 *            count = 0：删除等于value的所有元素。
	 * @param obj
	 *            待缓存的数据
	 * @return 成功删除的个数
	 */
	public long removeList(final String key, final long count, final String obj) {
		if (StringUtils.isEmpty(key)) {
			return 0;
		}
		return redisTemplate.opsForList().remove(key, count, obj);
	}

	/**
	 * 判断是否缓存过该数据
	 * 
	 * @param key
	 *            键
	 * @param obj
	 *            待缓存的数据
	 * @return 缓存过true/false
	 */
	public boolean getListExists(final String key, final String obj) {
		if (StringUtils.isEmpty(key)) {
			return false;
		}
		List<String> list = getList(key);
		return list.indexOf(obj) >= 0;
	}

	//============================ZSet=============================

	/**
	 * 给指定的集合添加 字段和分数
	 *
	 * @param key
	 *            缓存键值
	 * @param dataSet
	 *            缓存的数据
	 * @param score
	 *            分数
	 * @return 缓存数据的对象
	 */
	public <T> void setZSet(final String key, final String dataSet, final double score) {
		if (StringUtils.isEmpty(key)) {
			return;
		}
		redisTemplate.opsForZSet().add(key, dataSet, score);
	}

	/**
	 * 获取分数
	 *
	 * @param key
	 *            缓存键值
	 * @param dataSet
	 *            缓存的数据
	 */
	public double getZSetScore(final String key, final Object dataSet) {
		if (StringUtils.isEmpty(key)) {
			return 0;
		}
		return redisTemplate.opsForZSet().score(key, dataSet);
	}

	/**
	 * 判断是否缓存过该数据
	 * 
	 * @param key
	 *            键
	 * @param value
	 *            待缓存的数据
	 * @return 缓存过true/false
	 */
	public Cursor<ZSetOperations.TypedTuple<String>> getZSetScan(final String key) {
		return redisTemplate.opsForZSet().scan(key, ScanOptions.NONE);
	}

	/**
	 * 判断是否缓存过该数据
	 * 
	 * @param key
	 *            键
	 * @param value
	 *            待缓存的数据
	 * @return 缓存过true/false
	 */
	public boolean getZSetExists(final String key, final String value) {

		if (!exists(key)) {
			return false;
		}

		Cursor<ZSetOperations.TypedTuple<String>> cursor = redisTemplate.opsForZSet().scan(key, ScanOptions.NONE);
		while (cursor.hasNext()) {
			ZSetOperations.TypedTuple<String> item = cursor.next();
			if (item.getValue().equals(value)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * 获取排序后的结果
	 *
	 * @param key
	 *            缓存键值
	 * @param start
	 *            开始位置
	 * @param end
	 *            结束位置
	 */
	public Set<String> getZSetRange(final String key, final long start, final long end) {
		if (StringUtils.isEmpty(key)) {
			return null;
		}
		return redisTemplate.opsForZSet().reverseRange(key, start, end);
	}

	/**
	 * 获取排序后的结果
	 *
	 * @param key
	 *            缓存键值
	 * @param start
	 *            开始位置
	 * @param end
	 *            结束位置
	 */
	public Set<TypedTuple<String>> getZSetWithScores(final String key, final long start, final long end) {
		if (StringUtils.isEmpty(key)) {
			return null;
		}
		return redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);
	}

	/**
	 * 获取zset缓存的长度
	 *
	 * @param key
	 *            缓存键值
	 * @return 长度
	 */
	public long getZSetSize(final String key) {
		if (StringUtils.isEmpty(key)) {
			return 0;
		}
		try {
			return redisTemplate.opsForZSet().size(key);
		} catch (final Exception e) {
			e.printStackTrace();
			return 0;
		}
	}

	/**
	 * 给指定的集合移除字段
	 *
	 * @param key
	 *            缓存键值
	 * @param dataSet
	 *            缓存的数据
	 */
	public void removeZset(final String key, final Object dataSet) {
		if (StringUtils.isEmpty(key)) {
			return;
		}
		redisTemplate.opsForZSet().remove(key, dataSet);
	}

	//============================Set=============================
	/**
	 * 缓存Set
	 *
	 * @param key
	 *            缓存键值
	 * @param dataSet
	 *            缓存的数据
	 * @return 缓存数据的对象
	 */
	public void setSet(final String key, final Set<String> dataSet) {
		if (StringUtils.isEmpty(key)) {
			return;
		}
		final BoundSetOperations<String, String> setOperation = redisTemplate.boundSetOps(key);
		final Iterator<String> it = dataSet.iterator();
		while (it.hasNext()) {
			setOperation.add(it.next());
		}
	}

	/**
	 * 缓存Set
	 *
	 * @param key
	 *            缓存键值
	 * @param dataSet
	 *            缓存的数据
	 * @param time
	 *            时间(秒)
	 * @return 缓存数据的对象
	 */
	public void setSet(final String key, final Set<String> dataSet, final long time) {
		if (StringUtils.isEmpty(key)) {
			return;
		}
		final BoundSetOperations<String, String> setOperation = redisTemplate.boundSetOps(key);
		final Iterator<String> it = dataSet.iterator();
		while (it.hasNext()) {
			setOperation.add(it.next());
		}
		if (time > 0) {
			expire(key, time);
		}
	}

	/**
	 * 获得缓存的set
	 *
	 * @param key
	 *            缓存键值
	 * @return 缓存数据的对象
	 */
	public Set<String> getSet(final String key) {
		if (StringUtils.isEmpty(key)) {
			return null;
		}
		final Set<String> dataSet = new HashSet<String>(0);
		final BoundSetOperations<String, String> operation = redisTemplate.boundSetOps(key);

		final Long size = operation.size();
		for (int i = 0; i < size; i++) {
			dataSet.add(operation.pop());
		}
		return dataSet;
	}

	/**
	 * 获取set缓存的长度
	 *
	 * @param key
	 *            缓存键值
	 * @return 长度
	 */
	public long getSetSize(final String key) {
		if (StringUtils.isEmpty(key)) {
			return 0;
		}
		try {
			return redisTemplate.opsForSet().size(key);
		} catch (final Exception e) {
			e.printStackTrace();
			return 0;
		}
	}

	/**
	 * 移除值为value的
	 *
	 * @param key
	 *            缓存键值
	 * @param values
	 *            值 可以是多个
	 * @return 移除的个数
	 */
	public long removeSet(final String key, final Object... values) {
		if (StringUtils.isEmpty(key)) {
			return 0;
		}
		try {
			return redisTemplate.opsForSet().remove(key, values);
		} catch (final Exception e) {
			e.printStackTrace();
			return 0;
		}
	}

	//============================Map=============================
	/**
	 * 缓存Map
	 *
	 * @param key
	 *            缓存键值
	 * @param dataMap
	 */
	public void setMap(final String key, final Map<String, String> dataMap) {
		if (StringUtils.isEmpty(key)) {
			return;
		}
		final HashOperations<String, Object, Object> hashOperations = redisTemplate.opsForHash();
		if (null != dataMap) {
			for (final Map.Entry<String, String> entry : dataMap.entrySet()) {
				hashOperations.put(key, entry.getKey(), entry.getValue());
			}
		}
	}

	/**
	 * 获得缓存的Map
	 *
	 * @param key
	 *            缓存键值
	 * @return 缓存数据的对象
	 */
	public Map<Object, Object> getMap(final String key) {
		if (StringUtils.isEmpty(key)) {
			return null;
		}
		return redisTemplate.opsForHash().entries(key);
	}

	/**
	 * 缓存Map
	 *
	 * @param key
	 *            缓存键值
	 * @param dataMap
	 *            缓存数据的对象
	 */
	public void setIntegerMap(final String key, final Map<Integer, String> dataMap) {
		if (StringUtils.isEmpty(key)) {
			return;
		}
		final HashOperations<String, Integer, String> hashOperations = redisTemplate.opsForHash();
		if (null != dataMap) {
			for (final Map.Entry<Integer, String> entry : dataMap.entrySet()) {
				hashOperations.put(key, entry.getKey(), entry.getValue());
			}
		}
	}

	/**
	 * 获得缓存的Map
	 *
	 * @param key
	 *            缓存键值
	 * @return 缓存数据的对象
	 */
	public Map<Object, Object> getIntegerMap(final String key) {
		if (StringUtils.isEmpty(key)) {
			return null;
		}
		return redisTemplate.opsForHash().entries(key);
	}
}
